I Deployed a `coturn` Server to AWS
#coding #theWeb #learning #devOps #aws
Thanks to https://gabrieltanner.org/blog/turn-server/, last night I successfully deployed my first coturn
server on an aws ec2 instance. Why you might ask? I am building a thing which needs a WebRTC Peer Connection! Everything in the guide worked as advertised, but I want to have this repeatable. While Terraform is no longer the open source sweetheart it was when I started learning it 6 months ago, I wanted to build on the little knowledge I have of it. Plus I’m using it for personal reasons so it’s still fine as far as licenses are concerned.
Scripting the setup
please ignore the broken syntax highlighting and random backticks. There’s a bug somewhere and I’m in the process of fixing it but the bug is breaking my whole blog so I’d rather this one post be a bit broken than the whole blog. Thanks!
Maybe there is a better way, but it seems straightforward to add an init template that runs when the instance starts. The template is a bash script that installs and configures the coturn server, as well as certbot as the article above suggests. One thing that will need to be done in the terraform is opening the proper ports. The default security group won’t have the proper config so make sure not to skip that part, otherwise you’ll see timeout errors on the testing site.
You can choose to skip user credentials on the turnserver depending on your use case. I’m going to set them up via the suggested CLI args in the “docs”, which as far as I can tell is limited to this very well commented .conf
file.
This is meant for running as an init script on aws ec2. I am using ubuntu since I don’t want to deal withyum
or building the coturn
source.
Feel free to use the code below. No guarantees or warranties 😊.
The Script
TLDR: install coturn and certbot, configure them, and flip the ON switch!
#!/bin/sh
# shellcheck disable=SC2154
echo 'Initializing turn server...'
set -e
#ubuntu needs root
sudo -i
echo 'Installing stuff...'
#install coturn and cerbot
apt-get update -y
apt-get install coturn certbot -y
echo 'Setting config...'
# enable the server
cat >> /etc/default/coturn << EOF
TURNSERVER_ENABLED=1
EOF
# start the service
systemctl start coturn
#get the ip of the current ec2 instace
PUBLIC_IP=$(curl http://169.254.169.254/latest/meta-data/public-ipv4)
INTERNAL_IP=$(curl http://169.254.169.254/latest/meta-data/local-ipv4)
DOMAIN=${domain}
FQDN=${subdomain}.${domain}
EMAIL=${email}
COTURN_USER=${username}
COTURN_PASS=${password}
# Don't forget any DNS related credentials
USER_INFO=$COTURN_USER:$COTURN_PASS
# or for an encrypted pw:
# USER_INFO=$(turnadmin -k -u "$COTURN_USER" -r "$DOMAIN" -p "$COTURN_PASS" | head -n 1)
# set the config
cat >> /etc/turnserver.conf << EOF
realm=$DOMAIN
server-name=$FQDN
external-ip=$PUBLIC_IP/$INTERNAL_IP
cert=/etc/coturn/certs/$FQDN.cert
pkey=/etc/coturn/certs/$FQDN.key
user=$USER_INFO
lt-cred-mech
fingerprint
EOF
echo 'Creating DNS record...'
# update the dns records at your DNS provider with the public ip
# wait so that we can ensure the dns record is created and propogated
# might not be necessary but seemed safer than not waiting
sleep 30
echo 'Creating cert...'
# set the cert
certbot certonly -n --agree-tos --standalone --preferred-challenges http \
--deploy-hook "systemctl restart coturn" \
-d "$FQDN" \
-m "$EMAIL"
# was facing an issue like this without copying the certs to a dir
# that coturn could read:
# https://github.com/coturn/coturn/issues/1139
mkdir -p /etc/coturn/certs
cp "/etc/letsencrypt/live/$FQDN/cert.pem" "/etc/coturn/certs/$FQDN.cert"
cp "/etc/letsencrypt/live/$FQDN/privkey.pem" "/etc/coturn/certs/$FQDN.key"
chown turnserver -R "/etc/coturn/certs"
chmod 700 -R "/etc/coturn/certs"
service coturn restart
echo 'Done!'
The Terraform
And the terraform: TLDR: Spins up a keypair to be able to ssh into our ec2 instance, and the proper iam roles and permissions to do things in the least permissive way. A security group is also created with the proper ports open for our coturn server to talk to the rest of the web.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
required_version = ">= 1.2.0"
}
provider "aws" {
region = var.aws_region
}
locals {
user_data = templatefile("${path.module}/init.tftpl", {
subdomain = var.subdomain
domain = var.domain
username = var.coturn_user
password = var.coturn_pass
email = var.email
# any extra vars needed for DNS
})
}
# key pair used to ssh in to the ec2 instance
resource "aws_key_pair" "deployer" {
key_name = "coturn_keypair"
public_key = file(var.public_key)
}
resource "aws_instance" "app_server" {
instance_type = "t3.nano"
# ubuntu 22.10
ami = "ami-0fc5d935ebf8bc3bc"
key_name = aws_key_pair.deployer.key_name
iam_instance_profile = aws_iam_instance_profile.coturn.name
security_groups = [aws_security_group.coturn_sg.name]
root_block_device {
delete_on_termination = true
}
# init script
user_data = local.user_data
tags = {
Name = "Coturn"
}
}
# IAM roles and policy docs
# IAM role for the instance profile
resource "aws_iam_role" "role" {
name = "coturn_iam_role_ec2"
path = "/"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
# attach role to an instance profile for use with the ec2 instance
resource "aws_iam_instance_profile" "coturn" {
name = "coturn_iam_profile_ec2"
role = aws_iam_role.role.name
tags = {
Name = "CoturnAdmin"
}
}
# ec2 role
data "aws_iam_policy_document" "assume_role" {
statement {
sid = 1
effect = "Allow"
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
}
}
# security group to open ports
resource "aws_security_group" "coturn_sg" {
name = "coturn_sg"
description = "security group for the coturn ec2 instance"
ingress {
from_port = 3478
to_port = 3479
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 3478
to_port = 3479
protocol = "udp"
cidr_blocks = ["0.0.0.0/0"]
}
# tls
ingress {
from_port = 5349
to_port = 5350
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
And the tf vars file:
variable "aws_region" {
description = "AWS region"
type = string
default = "us-east-1"
}
variable "public_key" {
description = "path to AWS keypair public key"
type = string
default = "~/.ssh/aws_keypair.pub"
}
variable "domain" {
description = "the domain name of the server"
type = string
}
variable "subdomain" {
description = "the subdomain of the server"
type = string
}
variable "username" {
description = "the username for the lts creds"
type = string
}
variable "password" {
description = "the pass for the lts creds"
type = string
}
variable "email" {
description = "the email address for the ssl cert"
type = string
}
With these combined, you should be able to deploy a coturn
server with the flick of the terraform apply wrist!
If you have a new A record on your DNS provider, that means at least that step worked.
If you need to SSH into the instance, make sure to open up SSH port 22 in the security group for your IP. You can do this in the terraform if you want, but I wouldn’t expect to need to get in to the server much except to grab the encrypted password. Also the AWS console offers a nice “My IP” option. I know it’s possible to code this into the terraform but it seemed like too much effort for not a lot gained.
Test the server with https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
Resources
These resources were extremely helpful in learning and implementing all of this WebRTC stuff:
https://gabrieltanner.org/blog/turn-server https://web.dev/articles/webrtc-basics https://web.dev/articles/webrtc-infrastructure https://codelabs.developers.google.com/codelabs/webrtc-web/#0 https://www.baeldung.com/webrtc https://webrtc.github.io/samples/ https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
And for actually implementing WebRTC in an application, specifically SvelteKit, these were helpful. I ended up using Socket.io instead of ws
as implemented in the example:
https://github.com/suhaildawood/SvelteKit-integrated-WebSocket
This was helpful too as I had never worked with websockets before: https://joyofcode.xyz/using-websockets-with-sveltekit
Last updated: